package norbert.flickr.io {
	import com.adobe.crypto.MD5;
	import com.adobe.net.DynamicURLLoader;
	import com.adobe.webapis.URLLoaderBase;
	
	import flash.events.Event;
	import flash.net.URLRequest;
	import flash.xml.XMLDocument;
	import flash.xml.XMLNode;
	
	import mx.binding.utils.ChangeWatcher;
	import mx.core.IMXMLObject;
	import mx.events.PropertyChangeEvent;
	import mx.logging.ILogger;
	import mx.logging.Log;
	import mx.rpc.Fault;
	import mx.rpc.events.FaultEvent;
	import mx.rpc.events.InvokeEvent;
	import mx.rpc.events.ResultEvent;
	
	import norbert.commons.Entry;
	import norbert.flickr.Flickr;
	import norbert.flickr.api.Auth;
	
	[Event(name="fault", type="mx.rpc.events.FaultEvent")]
	[Event(name="invoke", type="mx.rpc.events.InvokeEvent")]
	[Event(name="result", type="mx.rpc.events.ResultEvent")]
	
	public class FlickrService extends URLLoaderBase implements IMXMLObject {
		private static var logger: ILogger = Log.getLogger("norbert.flickr.io.FlickrService");
		
		include "../../utils/include/eventHandlers.as";
		
		private var id: String;
		private var document: Object;
		
		private var _flickr: Flickr;
		[Bindable]
		public function get flickr(): Flickr {
			return _flickr;
		}
		public function set flickr(value: Flickr): void {
			_flickr = value;
			if (flickr && autoSend) {
				send();
			}
		}
		
		public var autoSend: Boolean = false;
		
		protected var methodBase: String;
		
		private var _method: String;
		public function get method(): String {
			return _method;
		}
		public function set method(value: String): void {
			_method = value;
		}
		
		[Bindable]
		public var lastResult: Object;
		
		private var _parameters: Array = [];
		public function get parameters(): Array {
			return _parameters;
		}
		public function set parameters(value: Array): void {
			_parameters = value;
			if (parameters) {
				for each (var item: Object in parameters) {
					var parameter: Entry = item as Entry;
					ChangeWatcher.watch(parameter, "value", onParameterChanged);
				}
			}
		}
		
		public var signed: Boolean = false;
		
		/* EVENT HANDLERS */
		
		private function onParameterChanged(event: PropertyChangeEvent): void {
			logger.debug("{0}: parameter changed", this);
			if (autoSend) {
				send();
			}
		}
		
		private function onComplete(event: Event): void {
			logger.debug("{0}: completed", this);
			
			var loader: DynamicURLLoader = event.target as DynamicURLLoader;
			var response: String = loader.data;
			
			// Use an XMLDocument to convert a string to XML
			var doc: XMLDocument = new XMLDocument();
			doc.ignoreWhite = true;
			doc.parseXML( response );

			// Get the root rsp node from the document
			var rsp: XMLNode = doc.firstChild;
			
			// Clean up a little
			doc = null; 
			
			if ( rsp.attributes.stat == "ok" ) {
				var result: Object;
				
				result = XML( rsp );
				
				logger.debug("{0}: result", this);

				if (dispatchEvent(ResultEvent.createEvent(result))) {
					lastResult = result;
				}
			} else {
				// In the event that we don't get an xml object
				// as part of the error returned, just
				// use the plain text as the error message
				
				var fault: Fault;
				if ( rsp.firstChild != null ) {
					fault = new Fault(rsp.firstChild.attributes.code, rsp.firstChild.attributes.msg);
				} else {
					fault = new Fault("-1", rsp.nodeValue);
				}
				
				logger.debug("{0}: fault: {1} - {2}", this, fault.faultCode, fault.faultString);

				if (dispatchEvent(FaultEvent.createEvent(fault))) {
					lastResult = null;
				}
			}
		}
		
		/* METHODS */
		
		public function initialized(document: Object, id: String): void {
			logger.debug("{0}: init", this);
			
			this.id = id;
			this.document = document;
		}
		
		public function send(): Boolean {
			logger.debug("{0}: send start", this);
			
			if (flickr) {
				for each (var item: Object in parameters) {
					if (item is Entry) {
						var param: Entry = item as Entry;
						logger.debug("{0}: param: " + param.name + " " + param.required, this);
						if (param.required && (param.value == null || param.value == "")) {
							logger.info("{0} - Required parameter is missing: {1}", this, param.name);
							return false;
						}
					}
				}

				// Create an array to store our name/value pairs
				// for the query because during signing we need to sort
				// these alphabetically
				var args:Array = new Array();
				
				args.push( new Entry( "api_key", flickr.api_key ) );
				args.push( new Entry( "method", methodBase + "." + method ) );
				
				if (parameters != null) {
					// Loop over the parameters and add them as arguments
					for ( var i:int = 0; i < parameters.length; i++ ) {
						// Do we have an argument name, or do we create one
						if ( parameters[i] is Entry ) {
							args.push( parameters[i] );
						} else {
							// Create a unique argument name using our loop counter
							args.push( new Entry( "param" + i, parameters[i].toString() ) );
						}
					}
				}
				
				// If a user is authenticated, automatically add their token
				if ( flickr.permission != Auth.PERMISSION_NONE && flickr.token ) {
					args.push( new Entry( "auth_token", flickr.token ) );
					// auto-sign the call because the user is authenticated
					signed = true;
				}
				
				// Sign the call if we have to, or if the user is logged in
				if ( signed ) {
					// sign the call according to the documentation point #8
					// here: http://www.flickr.com/services/api/auth.spec.html
					args.sortOn( "name" );
					var sig:String = flickr.secret;
					for ( var j:int = 0; j < args.length; j++ ) {
						sig += args[j].name.toString() + args[j].value.toString();	
					}	
					args.push( new Entry( "api_sig", MD5.hash( sig ) ) );
				}
				
				// Construct the query string to send to the Flickr service
				var query:String = "";
				for ( var k:int = 0; k < args.length; k++ ) {
					// This puts 1 too many "&" on the end, but that doesn't
					// affect the flickr call, so it doesn't matter
					query += args[k].name + "=" + args[k].value + "&";	
				}
				
				logger.debug("{0}: query: " + query, this);
				
				if (dispatchEvent(InvokeEvent.createEvent())) {
					// Use the "internal" flickrservice namespace to be able to
					// access the urlLoader so we can make the request.
					var loader: DynamicURLLoader = getURLLoader();
					
					// Construct a url request with our query string and invoke
					// the Flickr method
					loader.addEventListener( "complete", onComplete );
					loader.load( new URLRequest( Flickr.END_POINT + query ) );
					
					logger.debug("{0}: sent", this);
					
					return true;
				}
				
				return false;
			} else {
				logger.error("Flickr not set");
			}
			
			return false;
		}
		
		override public function toString(): String {
			return methodBase + "." + method + " (" + id + ")";
		}
		
	}
}
